﻿using System.Reflection;
using CapHttp.Core.Extensions;
using DotNetCore.CAP;
using DotNetCore.CAP.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace CapHttp.Core;

public class CapConsumerServiceSelector : ConsumerServiceSelector
{
    private readonly CapOptions _capOptions;
    
    public CapConsumerServiceSelector(IServiceProvider serviceProvider) : base(serviceProvider)
    {
        _capOptions = serviceProvider.GetRequiredService<IOptions<CapOptions>>().Value;
    }

    protected override IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromInterfaceTypes(IServiceProvider provider)
    {
        var descriptors = new List<ConsumerExecutorDescriptor>();
        descriptors.AddRange(base.FindConsumersFromInterfaceTypes(provider));

        var subscribeTypes = new[]
            { typeof(IDistributedEventSubscriber).GetTypeInfo(), typeof(ICapSubscribe).GetTypeInfo() };
        var serviceCollection = provider.GetRequiredService<IServiceCollection>();
        var services = serviceCollection.Where(x => x.ImplementationType != null || x.ImplementationFactory != null);

        foreach (var service in services)
        {
            var detectType = service.ImplementationType ?? service.ServiceType;
            if (subscribeTypes.All(x => !x.IsAssignableFrom(detectType))) continue;
            
            var actualType = service.ImplementationType;
            if (actualType == null && service.ImplementationFactory != null)
            {
                actualType = provider.GetRequiredService(service.ServiceType).GetType();
            }

            if (actualType == null)
            {
                throw new NullReferenceException(nameof(service.ServiceType));
            }

            descriptors.AddRange(GetTopicAttributesDescription(actualType.GetTypeInfo(), service.ServiceType.GetTypeInfo()));
            descriptors.AddRange(GetCustomDescription(actualType.GetTypeInfo(), service.ServiceType.GetTypeInfo()));
        }

        return descriptors;
    }

    protected override IEnumerable<ConsumerExecutorDescriptor> FindConsumersFromControllerTypes()
    {
        var executorDescriptorList = new List<ConsumerExecutorDescriptor>();

        var types = Assembly.GetEntryAssembly().ExportedTypes;
        foreach (var type in types)
        {
            var typeInfo = type.GetTypeInfo();
            if (Helper.IsController(typeInfo))
            {
                executorDescriptorList.AddRange(GetTopicAttributesDescription(typeInfo));
                executorDescriptorList.AddRange(GetCustomDescription(typeInfo));
            }
        }

        return executorDescriptorList;
    }

    private IEnumerable<ConsumerExecutorDescriptor> GetCustomDescription(TypeInfo typeInfo, TypeInfo? serviceTypeInfo = null)
    {
        foreach (var method in typeInfo.DeclaredMethods)
        {
            var attr = method.GetCustomAttribute<DistributedEventSubscribeAttribute>();
            if (attr == null) continue;

            if (attr.Group == null)
            {
                attr.Group = _capOptions.DefaultGroupName + "." + _capOptions.Version;
            }
            else
            {
                attr.Group = attr.Group + "." + _capOptions.Version;
            }

            if (!string.IsNullOrEmpty(_capOptions.GroupNamePrefix))
            {
                attr.Group = $"{_capOptions.GroupNamePrefix}.{attr.Group}";
            }

            var parameters = method.GetParameters()
                .Select(parameter => new ParameterDescriptor
                {
                    Name = parameter.Name,
                    ParameterType = parameter.ParameterType,
                    IsFromCap = parameter.GetCustomAttributes(typeof(FromCapAttribute)).Any()
                }).ToList();

            string eventName;
            if (!string.IsNullOrWhiteSpace(attr.Name))
            {
                eventName = attr.Name.Trim();
            }
            else if (attr.Type != null)
            {
                eventName = attr.Type.GetEventName();
            }
            else
            {
                eventName = parameters.First().ParameterType.GetEventName();
            }

            yield return new ConsumerExecutorDescriptor
            {
                Attribute = new CapSubscribeAttribute(eventName)
                {
                    Group = attr.Group
                },
                Parameters = parameters,
                MethodInfo = method,
                ImplTypeInfo = typeInfo,
                ServiceTypeInfo = serviceTypeInfo,
                TopicNamePrefix = _capOptions.TopicNamePrefix
            };
        }
    }
}